参数传递

  当一个函数被调用时,将安排好其形式参数所需要的存储,各个形式参数将用对应的实际参数进行初始化。参数传递的语义与初始化的语义完全相同。特别是,需要对照着每一个形式参数检查与之对应的实际参数的类型,并执行所有标准的或者用户定义的类型转换。另有一些特殊规则处理数组参数的传递(7.2.1节),一种传递不加检查的参数的机制(7.6节)以及一种刻画默认参数的机制(7.5节)。考虑

    void f(int val, int& ref)
    {
        val++;
        ref++;
    }

当f()被调用时,val++增加的是第一个实际参数的一个局部副本,而ref++增加的是第二个实际参数本身。例如,

    void g()
    {
        int i = 1;
        int j = 1;
        f(i, j);
    }

将使j增加1,而i并不增加。第一个参数i传递的是值,而第二个参数j传递的是引用。正如第5.5节里所说,修改引用参数的函数将会使程序更难阅读,因此最好避免写这种函数(但也请看看第21.3.2节)。当然也应注意到,通过引用方式传递大的对象,比通过值传递的效率更高一些。在这种情况下,可以将有关的参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值:

    void f(const Large& arg)
    {
        // 如果没有显式地做类型转换,“arg”的值就不能修改
    }

如果在一个引用参数的声明中没有const,就应该认为,这是想说明该参数将被修改:

void g(Large& arg);                 // 假定g()修改arg

与此类似,将指针参数声明为const,也就是告知读者,函数将不修改由这个参数所指的对象。例如,

    int strlen(const char*);            // 求C风格的字符串的长度
    char* strcpy(char* to, const char* from);    // 复制C风格的字符串
    int strcmp(const char*, const char*);        // 比较C风格的字符串

使用const参数的重要性将随着程序的规模增大而进一步增长。

  请注意,参数传递的语义不同于赋值的语义。对于const参数、引用参数和某些用户定义类型的参数(10.4.4.1节),这一点都非常重要。

  文字量、常量和需要转换的参数都可以传递给const&参数,但不能传递给非const的引用参数。允许对const T&参数进行转换,就保证了对这种参数所能提供的值集合,正好与通过一个临时量传递T参数的集合相同。例如,

    float fsqrt(const float&);            // Fortran风格的sqrt采用引用参数
    void g(double d)
    {
        float r = fsqrt(2.0f);            // 传递的是保存2.0f的临时量的引用
        r = fsqrt(r);                     // 传递r的引用
        r = fsqrt(d);                     // 传递的是保存float(d)的临时量的引用
    }

对非const引用参数不允许做类型转换(5.5节),这种规定能帮助我们避免了一种由于引入临时量而产生的可笑错误。例如,

    void update(float& i);
    void g(double d, float r)
    {
        update(2.0f);                      // 错误❌:const参数
        update(r);                         // 传递r的引用
        update(d);                         // 错误❌:要求类型转换
    }

如果允许所有这些调用,update()将会不声不响地去更新一个马上就会被删除的临时量。这经常会让程序员极不愉快地大吃一惊。

🔚